﻿using System.Collections;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
using System.Text.RegularExpressions;
using Mapster;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Configuration;
using Newtonsoft.Json;

namespace Devonline.Core;

public static class CommonExtensions
{
    /// <summary>
    /// 值类型直转
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public static T? To<T>(this IConvertible value)
    {
        try
        {
            var result = Convert.ChangeType(value, typeof(T), CultureInfo.CurrentCulture);
            return result == null ? default : (T)result;
        }
        catch
        {
            return default;
        }
    }
    /// <summary>
    /// 获取属性的值, 此处只处理 公共成员属性
    /// </summary>
    /// <param name="obj">The object that have the property</param>
    /// <param name="propertyName">property name, support the format of "A.B.C"</param>
    /// <param name="ignoreCase">是否忽略大小写, 默认: 是</param>
    /// <returns></returns>
    public static object? GetPropertyValue(this object obj, string propertyName, bool ignoreCase = true)
    {
        var bindingFlags = BindingFlags.Public | BindingFlags.Instance;
        if (ignoreCase)
        {
            bindingFlags |= BindingFlags.IgnoreCase;
        }

        if (obj != null && !string.IsNullOrEmpty(propertyName))
        {
            if (obj is IEnumerable enumerable)
            {
                //TODO TBC 模式匹配改造
                //(obj as ICollection).Cast<object>().ToList().ForEach(x => { x = x.GetPropertyValue(current); });
                object[] array = enumerable.Cast<object>().ToArray();
                var result = new object[array.Length];
                for (int i = 0; i < array.Length; i++)
                {
                    var current = array[i].GetPropertyValue(propertyName, ignoreCase);
                    if (current != null)
                    {
                        result[i] = current;
                    }
                }

                return result;
            }
            else
            {
                int position = propertyName.IndexOf(AppSettings.CHAR_DOT, StringComparison.CurrentCulture);
                string current = position > 0 ? propertyName[..position] : propertyName;
                var propertyInfo = obj.GetType().GetProperty(current, bindingFlags);
                if (propertyInfo != null && propertyInfo.CanRead)
                {
                    var inner = propertyInfo.GetValue(obj);
                    if (inner != null && position > 0)
                    {
                        string next = propertyName[(position + 1)..];
                        inner = inner.GetPropertyValue(next, ignoreCase);
                    }

                    return inner;
                }
            }
        }

        return default;
    }
    /// <summary>
    /// 获取对象中属性的值, 此处只处理 公共成员属性
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="t"></param>
    /// <param name="propertyName"></param>
    /// <param name="ignoreCase">是否忽略大小写, 默认: 是</param>
    /// <returns></returns>
    public static TValue? GetPropertyValue<T, TValue>(this T t, string propertyName, bool ignoreCase = true)
    {
        var bindingFlags = BindingFlags.Public | BindingFlags.Instance;
        if (ignoreCase)
        {
            bindingFlags |= BindingFlags.IgnoreCase;
        }

        var value = typeof(T).GetProperty(propertyName, bindingFlags)?.GetValue(t);
        return value == null ? default : (TValue)value;
    }
    /// <summary>
    /// 为对象的属性 property 设置 value 值, 此处只处理 公共成员属性
    /// </summary>
    /// <param name="t">要设置属性的对象</param>
    /// <param name="propertyName">属性名称</param>
    /// <param name="value">要设置的值</param>
    /// <param name="ignoreCase">是否忽略大小写, 默认: 是</param>
    /// <returns>是否设置成功</returns>
    public static void SetPropertyValue<T>(this T t, string propertyName, object? value = default, bool ignoreCase = true, string? format = default)
    {
        if (!string.IsNullOrWhiteSpace(propertyName))
        {
            var bindingFlags = BindingFlags.Public | BindingFlags.Instance;
            if (ignoreCase)
            {
                bindingFlags |= BindingFlags.IgnoreCase;
            }

            var propertyInfo = typeof(T).GetProperty(propertyName, bindingFlags);
            if (propertyInfo != null && propertyInfo.CanWrite)
            {
                //value 有值, 可转换且类型和 PropertyInfo.PropertyType 不相同, 则先转换类型在赋值
                var propertyType = propertyInfo.PropertyType.GetCoreType();
                if (value != null && value is IConvertible && !value.GetType().GetCoreType().IsFromType(propertyType))
                {
                    if (value is string strValue)
                    {
                        if (propertyType.IsEnum)
                        {
                            value = Enum.Parse(propertyType, strValue);
                        }
                        else if (Type.GetTypeCode(propertyType) == TypeCode.DateTime)
                        {
                            if (string.IsNullOrWhiteSpace(format))
                            {
                                value = Convert.ToDateTime(strValue);
                            }
                            else
                            {
                                value = DateTime.ParseExact(strValue, format, CultureInfo.InvariantCulture);
                            }
                        }
                    }
                    else
                    {
                        value = Convert.ChangeType(value, propertyType, CultureInfo.CurrentCulture);
                    }
                }

                propertyInfo.SetValue(t, value);
            }
        }
    }

    /// <summary>
    /// 从缓存中读取值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public static T? GetValue<T>(this IDistributedCache cache, string key)
    {
        var value = cache.GetString(key);
        return value == null ? default : value.ToJsonObject<T>();
    }
    /// <summary>
    /// 从缓存中读取值
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache"></param>
    /// <param name="key"></param>
    /// <returns></returns>
    public static async Task<T?> GetValueAsync<T>(this IDistributedCache cache, string key)
    {
        var value = await cache.GetStringAsync(key);
        return value == null ? default : value.ToJsonObject<T>();
    }
    /// <summary>
    /// 数据写入缓存中
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache"></param>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public static void SetValue<T>(this IDistributedCache cache, string key, T value)
    {
        cache.SetString(key, value.ToJsonString());
    }
    /// <summary>
    /// 数据写入缓存中
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="cache"></param>
    /// <param name="key"></param>
    /// <param name="value"></param>
    public static async Task SetValueAsync<T>(this IDistributedCache cache, string key, T value)
    {
        await cache.SetStringAsync(key, value.ToJsonString());
    }

    /// <summary>
    /// 从配置文件配置项读取并初始化制定类型
    /// </summary>
    /// <typeparam name="TSetting">配置项类型</typeparam>
    /// <returns></returns>
    public static TSetting GetSetting<TSetting>(this IConfiguration configuration) where TSetting : class, new()
    {
        var setting = configuration.GetSetting<TSetting>(nameof(AppSetting)) ?? new TSetting();
        if (setting is AppSetting appSetting)
        {
            appSetting.Hosts ??= configuration.GetValue<string>(nameof(appSetting.Hosts));
            appSetting.Urls ??= configuration.GetValue<string>(nameof(appSetting.Urls));
            appSetting.AllowedHosts ??= configuration.GetValue<string>(nameof(appSetting.AllowedHosts));
            appSetting.CorsOrigins ??= configuration.GetValue<string>(nameof(appSetting.CorsOrigins));
            appSetting.IdentityDbContext ??= configuration.GetConnectionString(nameof(appSetting.IdentityDbContext));
            appSetting.ApplicationDbContext ??= configuration.GetConnectionString(nameof(appSetting.ApplicationDbContext));
            appSetting.CacheConfiguration ??= configuration.GetConnectionString(nameof(appSetting.CacheConfiguration));
        }

        return setting;
    }
    /// <summary>
    /// 将配置文件中 section 节点的值转换为指定类型
    /// 如果配置项中包含: 字符, 则直接返回配置项的确定值而不是整个配置节点的转换值
    /// </summary>
    /// <typeparam name="TSetting">配置项类型</typeparam>
    /// <param name="configuration">配置文件</param>
    /// <param name="key">配置项</param>
    /// <returns></returns>
    public static TSetting GetSetting<TSetting>(this IConfiguration configuration, string key) => key.Contains(AppSettings.CHAR_COLON) ? configuration.GetValue<TSetting>(key) : configuration.GetSection(key).Get<TSetting>();

    /// <summary>
    /// 对象转 json 字符串, 泛型用法
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="t"></param>
    /// <returns></returns>
    public static string ToJsonString<T>(this T t) => JsonConvert.SerializeObject(t, AppSettings.JsonSerializerSettings);
    /// <summary>
    /// 对象转 json 字符串, 非泛型用法
    /// </summary>
    /// <param name="value"></param>
    /// <param name="type"></param>
    /// <returns></returns>
    public static string ToJsonString(this object value) => JsonConvert.SerializeObject(value, AppSettings.JsonSerializerSettings);

    /// <summary>
    /// 可以处理复杂映射
    /// 用法 source.Mapper(s => new Target { PropertyName = s.PropertyName });
    /// </summary>
    /// <typeparam name="TSource">输入类</typeparam>
    /// <typeparam name="TTarget">输出类</typeparam>
    /// <param name="expression">表达式目录树,可以为null</param>
    /// <param name="source">输入实例</param>
    /// <returns></returns>
    public static TTarget Mapper<TSource, TTarget>(this TSource source, Expression<Func<TSource, TTarget>>? expression = null)
    {
        var sourceType = typeof(TSource);
        var targetType = typeof(TTarget);
        var memberBindingList = new List<MemberBinding>();
        var parameterExpression = Expression.Parameter(sourceType, AppSettings.DEFAULT_LAMBDA_PARAMETER);
        if (expression != null)
        {
            parameterExpression = expression.Parameters[AppSettings.UNIT_ZERO];
            if (expression.Body != null && expression.Body is MemberInitExpression member)
            {
                memberBindingList.AddRange(member.Bindings);
            }
        }

        foreach (var item in targetType.GetProperties())
        {
            var propertyInfo = sourceType.GetProperty(item.Name);
            if (propertyInfo != null)
            {
                var property = Expression.Property(parameterExpression, propertyInfo);
                memberBindingList.Add(Expression.Bind(item, property));
            }
        }

        foreach (var item in targetType.GetFields())
        {
            var fieldInfo = sourceType.GetField(item.Name);
            if (fieldInfo != null)
            {
                var property = Expression.Field(parameterExpression, fieldInfo);
                memberBindingList.Add(Expression.Bind(item, property));
            }
        }

        var memberInitExpression = Expression.MemberInit(Expression.New(targetType), memberBindingList.ToArray());
        var lambda = Expression.Lambda<Func<TSource, TTarget>>(memberInitExpression, new ParameterExpression[] { parameterExpression });
        return lambda.Compile().Invoke(source);
    }

    /// <summary>
    /// 复制对象
    /// </summary>
    /// <typeparam name="T">目标类型</typeparam>
    /// <param name="t">原始类型对象</param>
    /// <returns>复制得到的目标类型对象</returns>
    public static T Copy<T>([DisallowNull] this T t) => t.Adapt<T>();
    /// <summary>
    /// 将原始对象复制到新的目标类型对象
    /// </summary>
    /// <param name="value">原始对象</param>
    /// <typeparam name="T">目标类型</typeparam>
    /// <returns></returns>
    public static T CopyTo<T>(this object value) => value.Adapt<T>();
    /// <summary>
    /// 将原始对象复制到新的目标类型对象
    /// </summary>
    /// <param name="value">原始对象</param>
    /// <typeparam name="T">目标类型</typeparam>
    /// <returns></returns>
    public static TTarget CopyTo<TSource, TTarget>(this TSource source) => source.Adapt<TSource, TTarget>();
    /// <summary>
    /// 将原始对象复制到新的目标类型对象
    /// </summary>
    /// <param name="source">原始对象</param>
    /// <param name="target">目标对象</param>
    /// <typeparam name="TSource">原始类型</typeparam>
    /// <typeparam name="TTarget">目标类型</typeparam>
    /// <returns></returns>
    public static TTarget CopyTo<TSource, TTarget>(this TSource source, TTarget target) => source.Adapt(target);
    /// <summary>
    /// 将原始对象复制到新的目标类型对象
    /// </summary>
    /// <param name="source">原始对象</param>
    /// <param name="targetType">目标对象类型</param>
    /// <returns></returns>
    public static object? CopyTo(this object value, Type type) => value.Adapt(value.GetType().GetCoreType(), type);
    /// <summary>
    /// 将原始对象复制到新的目标类型对象
    /// </summary>
    /// <param name="source">原始对象</param>
    /// <param name="targetType">目标对象类型</param>
    /// <returns></returns>
    public static object? CopyTo(this object source, object target) => source.Adapt(source.GetType().GetCoreType(), target.GetType().GetCoreType());
    /// <summary>
    /// 将目标对象列表中的属性值合并到源对象中
    /// </summary>
    /// <param name="source">原始对象</param>
    /// <param name="targets">待合并对象列表</param>
    /// <returns></returns>
    public static object? Merge(this object source, params object[] targets)
    {
        if (source != null && targets.Length != 0)
        {
            var sourceType = source.GetType().GetCoreType();
            foreach (var target in targets)
            {
                source.Adapt(target, sourceType, target.GetType().GetCoreType());
            }
        }

        return source;
    }

    /// <summary>
    /// 获取对象类型特性的指定属性值
    /// </summary>
    /// <param name="member">待获取对象</param>
    /// <param name="propertyName">用于获取值的特性上的属性名称</param>
    /// <returns>显示名称</returns>
    public static TValue? GetAttributeValue<TAttribute, TValue>(this object obj, string propertyName) where TAttribute : Attribute
    {
        if (obj == null)
        {
            throw new ArgumentNullException(nameof(obj));
        }

        return obj.GetType().GetAttributeValue<TAttribute, TValue>(propertyName);
    }
    /// <summary>
    /// 获取对象类型成员特性的指定属性值
    /// </summary>
    /// <param name="member">待获取对象</param>
    /// <param name="memberName">成员名称, 可以是属性或者字段</param>
    /// <param name="propertyName">用于获取值的特性上的属性名称</param>
    /// <returns>显示名称</returns>
    public static TValue? GetAttributeValue<TAttribute, TValue>(this object obj, string memberName, string propertyName) where TAttribute : Attribute
    {
        if (obj == null)
        {
            throw new ArgumentNullException(nameof(obj));
        }

        var member = obj.GetType().GetMember(memberName)?.FirstOrDefault();
        return member != null ? member.GetAttributeValue<TAttribute, TValue>(propertyName) : default;
    }

    /// <summary>
    /// 获取对象的类型上可显示名字
    /// 可用于获取枚举值的可显示名字
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public static string GetDisplayName([DisallowNull] this object obj) => obj.GetType().GetDisplayName();
    /// <summary>
    /// 获取对象的属性上可显示名字
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="memberName">成员名称, 可以是属性或者字段</param>
    /// <returns></returns>
    public static string? GetDisplayName([DisallowNull] this object obj, string memberName) => obj.GetType().GetDisplayName(memberName);
    /// <summary>
    /// 获取对象上的隐射到数据库的字段名字(Column 特性的 Name 值)
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="memberName">成员名称, 可以是属性或者字段</param>
    /// <returns></returns>
    public static string? GetColumnName([DisallowNull] this object obj, string memberName) => obj.GetType().GetMember(memberName).FirstOrDefault()?.GetColumnName();
    /// <summary>
    /// 获取对象上的隐射到数据库的表名(Table 特性的 Name 值)
    /// </summary>
    /// <returns></returns>
    public static string? GetTableName([DisallowNull] this object obj) => obj.GetType().GetTableName();
    /// <summary>
    /// 获取对象设置的 json 
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="memberName"></param>
    /// <returns></returns>
    public static string? GetJsonPropertyName([DisallowNull] this object obj, string memberName) => obj.GetType().GetJsonPropertyName(memberName);

    /// <summary>
    /// 获取时间差的详情, 精确到 kind 指定值
    /// </summary>
    /// <param name="timeSpan"></param>
    /// <returns></returns>
    public static string GetTimeDetail(this TimeSpan timeSpan, TimeKind kind = TimeKind.MilliSecond)
    {
        var result = new List<string>();
        if (timeSpan.Days > 0 && kind >= TimeKind.Day)
        {
            if (timeSpan.Days > AppSettings.UNIT_DAYS_A_YEAR && kind >= TimeKind.Year)
            {
                int diff = timeSpan.Days % AppSettings.UNIT_DAYS_A_YEAR;
                result.Add($"{timeSpan.Days / AppSettings.UNIT_DAYS_A_YEAR} 年");
                if (diff > 0)
                {
                    result.Add($"{diff} 天");
                }
            }
            else
            {
                result.Add($"{timeSpan.Days} 天");
            }
        }

        if (timeSpan.Hours > 0 && kind >= TimeKind.Hour)
        {
            result.Add($"{timeSpan.Hours} 小时");
        }

        if (timeSpan.Minutes > 0 && kind >= TimeKind.Minute)
        {
            result.Add($"{timeSpan.Minutes} 分钟");
        }

        if (timeSpan.Seconds > 0 && kind >= TimeKind.Second)
        {
            result.Add($"{timeSpan.Seconds} 秒");
        }

        if (timeSpan.Milliseconds >= 0 && kind >= TimeKind.MilliSecond)
        {
            result.Add($"{timeSpan.Milliseconds} 毫秒");
        }

        return string.Join(AppSettings.CHAR_SPACE, result);
    }
    /// <summary>
    /// 获取相对于当前时间时间差的详情, 精确到 kind 指定值
    /// </summary>
    /// <param name="dateTime"></param>
    /// <param name="kind"></param>
    /// <returns></returns>
    public static string GetTimeDetail(this DateTime dateTime, TimeKind kind = TimeKind.MilliSecond) => (DateTime.Now - dateTime).GetTimeDetail(kind);

    /// <summary>
    /// get inner exception message of a exception
    /// </summary>
    /// <param name="ex">Exception instance</param>
    /// <returns>inner exception message</returns>
    public static string GetInnerException(this Exception ex)
    {
        if (ex == null)
        {
            throw new ArgumentNullException(nameof(ex));
        }

        if (ex.InnerException != null)
        {
            return ex.InnerException.GetInnerException();
        }

        return ex.Message;
    }
    /// <summary>
    /// 错误信息和堆栈一起返回
    /// </summary>
    /// <param name="ex"></param>
    /// <returns></returns>
    public static string GetMessage(this Exception ex) => $"错误信息: {ex.GetInnerException()}, 错误堆栈: {ex.StackTrace}";

    /// <summary>
    /// 反射调用对象的泛型方法
    /// </summary>
    /// <param name="caller">调用对象</param>
    /// <param name="methodName">方法名</param>
    /// <param name="parameters">参数列表</param>
    /// <param name="returnValue">返回值</param>
    /// <param name="genericTypes">泛型类型</param>
    public static void InvokeGenericMethod(this object caller, string methodName, object[]? parameters, out object? returnValue, params Type[] genericTypes)
    {
        if (caller == null)
        {
            throw new ArgumentNullException(nameof(caller));
        }

        returnValue = null;
        var methodInfo = caller.GetType().GetMethods().FirstOrDefault(x => x.IsGenericMethod && x.Name == methodName && x.GetParameters().Length == (parameters?.Length ?? 0));
        if (methodInfo != null)
        {
            returnValue = methodInfo.MakeGenericMethod(genericTypes).Invoke(caller, parameters);
        }
    }
    /// <summary>
    /// 反射调用对象的泛型方法
    /// </summary>
    /// <typeparam name="T">返回值类型</typeparam>
    /// <param name="caller">调用对象</param>
    /// <param name="methodName">方法名</param>
    /// <param name="parameters">参数列表</param>
    /// <param name="genericTypes">泛型类型</param>
    /// <returns>返回调用结果</returns>
    public static T? InvokeGenericMethod<T>(this object caller, string methodName, object[]? parameters, params Type[] genericTypes) where T : class
    {
        if (caller == null)
        {
            throw new ArgumentNullException(nameof(caller));
        }

        var methodInfo = caller.GetType().GetMethods().FirstOrDefault(x => x.IsGenericMethod && x.Name == methodName && x.GetParameters().Length == (parameters?.Length ?? 0));
        if (methodInfo != null)
        {
            var returnValue = methodInfo.MakeGenericMethod(genericTypes).Invoke(caller, parameters);
            return returnValue == null ? default : (T)returnValue;
        }

        return default;
    }

    /// <summary>
    /// get string includes variables value from string variables expression
    /// usage (eg in user model):
    /// string value = "this is template string that contains Name: {Name} and Department: {Department.Name}";
    /// value = user.GetVariablesExpression(value);
    /// then value will be "this is template string that contains Name: Alex and Department: Huatek"
    /// </summary>
    /// <param name="obj">which object will be calculated to get value</param>
    /// <param name="value">which value expression will be calculated</param>
    /// <param name="prefix">veriable prrfox, default is "{"</param>
    /// <param name="suffix">veriable suffix, default is "}"</param>
    /// <param name="separator">veriable separator, default is "."</param>
    /// <returns></returns>
    public static string GetVariablesExpression(this object obj, string value, string prefix = "{", string suffix = "}", string separator = ".")
    {
        if (value.IsNotNullOrEmpty() && value.Contains(prefix, StringComparison.InvariantCultureIgnoreCase) && value.Contains(suffix, StringComparison.InvariantCultureIgnoreCase))
        {
            string exp = prefix + @"(\w+" + separator + "?)+?" + suffix;
            return Regex.Replace(value, exp, new MatchEvaluator(match =>
            {
                var result = string.Empty;
                if (match.Value.Length > prefix.Length + suffix.Length)
                {
                    var propertyName = match.Value.Replace(prefix, string.Empty, StringComparison.InvariantCultureIgnoreCase).Replace(suffix, string.Empty, StringComparison.InvariantCultureIgnoreCase);
                    var value = obj.GetPropertyValue(propertyName);
                    if (value != null)
                    {
                        result = value switch
                        {
                            string str => str,
                            DateTime dateTime => dateTime.ToString(AppSettings.DEFAULT_DATETIME_FORMAT, CultureInfo.CurrentCulture),
                            DateTimeOffset dateTimeOffset => dateTimeOffset.ToString(AppSettings.DEFAULT_DATETIME_FORMAT, CultureInfo.CurrentCulture),
                            IEnumerable enumerable => enumerable.Cast<object>().ToList().ToString<object>(),
                            _ => value.ToString(),
                        };
                    }
                }

                return result ?? string.Empty;
            }), RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.Multiline);
        }

        return string.Empty;
    }
}
